$svnExe="svn.exe"


Function Parse-XmlResult
{
    [CmdletBinding()]
	param
	(
		[parameter(Mandatory = $true,ValueFromPipeline=$true)]
		[PsObject]
		$SvnResult
	)
    Process
    {
    
        #If command status is false, parsing is not necessary because it will fail wioth a xml parsing error
        if ($SvnResult.Status)
        {

            try
            {
                [Xml]$xmlResultACtion = $SvnResult.InternalResult
                $SvnResult.Result = $xmlResultACtion            
            }
            catch [Exception]
            {
                $SvnResult | Add-Member -MemberType NoteProperty -Name ParsingResultError -Value $_.Exception
                $svnResult.ExitCode = -1
                $SvnResult.Status = $false
            }
        }
        Write-Output $Svnresult
    }        
}



Function Get-SvnInfo
{
    [CmdletBinding()]
	param
	(
		[parameter(Mandatory = $true)]
		[System.String]
		$Path,

		[parameter(Mandatory = $false)]
	    [System.String]
		$UserName,

		[parameter(Mandatory = $false)]
	    [System.String]
		$Password
	)
     Begin
    {
        $watch = [system.diagnostics.stopwatch]::StartNew() 
        write-verbose "Start Get-SvnInfo for $Path"
        $Path = EnsureCorrectPath -Path $Path
    }
    End
    {
        write-verbose "End Get-SvnInfo for $Path"
        $watch.Stop();
        Write-Verbose "Get-SvnInfo duration : $($watch.Elapsed)"
    }
    Process
    {

        $SvnCommand = [String]::Format("{0} info {1} --xml", $svnExe, $Path)
        $svnResult = Invoke-SvnCommand -SvnCommand $SvnCommand -UserName $UserName -Password $Password | Parse-XmlResult

        if ($svnResult.Status -and $svnResult.Result)
        {
            $xmlResultCommand=Parse-InfoCommand -SvnResult $svnResult.Result.Info
            $svnResult.Result = $xmlResultCommand
        }
        else
        {
            if (($svnResult.InternalResult -join ";") -eq '<?xml version="1.0"?>;<info>')
            {
                $svnResult.Result = "svn: '$Path' is not a working copy"
                $SvnCleanCommand = [String]::Format("{0} help", $svnExe)
                $SvnCleanResult = Invoke-SvnCommand -SvnCommand $SvnCleanCommand
            }
        }
        Write-Output $svnResult
    }
}

Function Get-LocalSvnStatus
{
    [CmdletBinding()]
	param
	(
		[parameter(Mandatory = $true)]
		[System.String]
		$Path,

		[parameter(Mandatory = $false)]
	    [System.String]
		$UserName,

		[parameter(Mandatory = $false)]
	    [System.String]
		$Password
	)
     Begin
    {
        $watch = [system.diagnostics.stopwatch]::StartNew() 
        write-verbose "Start Get-LocalSvnStatus for $Path"
        $Path = EnsureCorrectPath -Path $Path
    }
    End
    {
        write-verbose "End Get-LocalSvnStatus for $Path"
        $watch.Stop();
        Write-Verbose "Get-LocalSvnStatus duration : $($watch.Elapsed)"
    }
    Process
    {

        $SvnCommand = [String]::Format("{0} status {1} --xml", $svnExe, $Path)
        $svnResult = Invoke-SvnCommand -SvnCommand $SvnCommand -UserName $UserName -Password $Password | Parse-XmlResult

        if ($svnResult.Status -and $svnResult.Result)
        {
            $xmlResultCommand=Parse-StatusCommand -SvnResult $svnResult.Result.Status
            $svnResult.Result = $xmlResultCommand
        }
        else
        {
            if (($svnResult.InternalResult -join ";") -eq '<?xml version="1.0"?>;<status>')
            {
                $svnResult.Result = "svn: '$Path' is not a working copy"
                $SvnCleanCommand = [String]::Format("{0} help", $svnExe)
                $SvnCleanResult = Invoke-SvnCommand -SvnCommand $SvnCleanCommand
            }

        }
        Write-Output $svnResult
    }
}

Function Get-RemoteSvnStatus
{
    [CmdletBinding()]
	param
	(
		[parameter(Mandatory = $true)]
		[System.String]
		$Path,

		[parameter(Mandatory = $false)]
	    [System.String]
		$UserName,

		[parameter(Mandatory = $false)]
	    [System.String]
		$Password
	)
     Begin
    {
        $watch = [system.diagnostics.stopwatch]::StartNew() 
        write-verbose "Start Get-RemoteSvnStatus for $Path"
        $Path = EnsureCorrectPath -Path $Path
    }
    End
    {
        write-verbose "End Get-RemoteSvnStatus for $Path"
        $watch.Stop();
        Write-Verbose "Get-RemoteSvnStatus duration : $($watch.Elapsed)"
    }
    Process
    {

        $SvnCommand = [String]::Format("{0} status {1} --show-updates --xml", $svnExe, $Path)
        $svnResult = Invoke-SvnCommand -SvnCommand $SvnCommand -UserName $UserName -Password $Password | Parse-XmlResult

        if ($svnResult.Status -and $svnResult.Result)
        {
            $xmlResultCommand=Parse-StatusCommand -SvnResult $svnResult.Result.Status
            $svnResult.Result = $xmlResultCommand
        }
        else
        {
            if (($svnResult.InternalResult -join ";") -eq '<?xml version="1.0"?>;<status>')
            {
                $svnResult.Result = "svn: '$Path' is not a working copy"
                $SvnCleanCommand = [String]::Format("{0} help", $svnExe)
                $SvnCleanResult = Invoke-SvnCommand -SvnCommand $SvnCleanCommand
            }

        }
        Write-Output $svnResult
    }
}


Function Clear-SvnWorkspace
{
    [CmdletBinding()]
	param
	(
		[parameter(Mandatory = $true)]
		[System.String]
		$Path,

		[parameter(Mandatory = $false)]
	    [System.String]
		$UserName,

		[parameter(Mandatory = $false)]
	    [System.String]
		$Password
	)
     Begin
    {
        $watch = [system.diagnostics.stopwatch]::StartNew() 
        write-verbose "Start Clear-SvnWorkspace for $Path"
        $Path = EnsureCorrectPath -Path $Path
    }
    End
    {
        write-verbose "End Clear-SvnWorkspace for $Path"
        $watch.Stop();
        Write-Verbose "Clear-SvnWorkspace duration : $($watch.Elapsed)"
    }
    Process
    {

        $SvnCommand = [String]::Format("{0} cleanup {1}", $svnExe, $Path)
        $svnResult= Invoke-SvnCommand -SvnCommand $SvnCommand -UserName $UserName -Password $Password
        Write-Output $svnResult
    }
}

Function Restore-SvnWorkspace
{
    [CmdletBinding()]
	param
	(
		[parameter(Mandatory = $true)]
		[System.String]
		$Path,

		[parameter(Mandatory = $false)]
	    [System.String]
		$UserName,

		[parameter(Mandatory = $false)]
	    [System.String]
		$Password
	)
     Begin
    {
        $watch = [system.diagnostics.stopwatch]::StartNew() 
        write-verbose "Start Restore-SvnWorkspace for $Path"
        $Path = EnsureCorrectPath -Path $Path
    }
    End
    {
        write-verbose "End Restore-SvnWorkspace for $Path"
        $watch.Stop();
        Write-Verbose "Restore-SvnWorkspace duration : $($watch.Elapsed)"
    }
    Process
    {

        $SvnCommand = [String]::Format("{0} revert {1}", $svnExe, $Path)
        $svnResult= Invoke-SvnCommand -SvnCommand $SvnCommand -UserName $UserName -Password $Password
        Write-Output $svnResult
    }
}


Function Get-SvnWorkspace
{
    [CmdletBinding()]
	param
	(
		[parameter(Mandatory = $true)]
		[System.String]
		$Path,

		[parameter(Mandatory = $true)]
		[System.Uri]
		$Uri,

		[parameter(Mandatory = $false)]
	    [System.String]
		$UserName,

		[parameter(Mandatory = $false)]
	    [System.String]
		$Password,

		[parameter(Mandatory = $false)]
		[System.Uint32]
		$Revision,

		[parameter(Mandatory = $false)]
		[Switch]
		$Switch


	)
     Begin
    {
        $watch = [system.diagnostics.stopwatch]::StartNew() 
        write-verbose "Start Get-SvnWorkspace from $Uri to $Path"
        $Path = EnsureCorrectPath -Path $Path

    }
    End
    {
        write-verbose "End Get-SvnWorkspace from $Uri to $Path"
        $watch.Stop();
        Write-Verbose "Get-SvnWorkspace duration : $($watch.Elapsed)"
    }
    Process
    {

        $subCommand = "co"
        if ($Switch) {$subCommand = "sw"}

        $SvnCommand = [String]::Format("{0} {1} {2} {3}", $svnExe, $subCommand, $($Uri.AbsoluteUri), $Path)
        
        if ($Revision -gt 0) {$svnCommand += " -r $Revision"}

        $svnStatus = Invoke-SvnCommand -SvnCommand $SvnCommand -UserName $UserName -Password $Password

        Write-Output $svnStatus
    }
}



function Invoke-SvnCommand
{
    [cmdletbinding(DefaultParameterSetName="none")]
	param
	(
		[parameter(Mandatory = $true)]
		[System.String]
		$SvnCommand,

		[parameter(Mandatory = $false)]
	    [System.String]
		$UserName,

		[parameter(Mandatory = $false)]
	    [System.String]
		$Password

	)
    Begin
    {
        $watch = [system.diagnostics.stopwatch]::StartNew() 
        write-verbose "Start Invoke-SvnCommand for $SvnCommand"
    }
    End
    {
        write-verbose "End Invoke-SvnCommand for $SvnCommand"
        $watch.Stop();
        Write-Verbose "Invoke-SvnCommand duration : $($watch.Elapsed)"
    }
    Process
    {

        #Ensure svn command credential is ok
        $SvnCommand = Ensure-SvnCredential -SvnCommand $SvnCommand -UserName $UserName -Password $Password


        $resultAction = Invoke-ManagedCommand -CommandToExecute $svnCommand 
        $resultAction | Add-Member -MemberType NoteProperty -Name Result -Value $resultAction.InternalResult

        Write-Output $resultAction
    }
}

Function Parse-InfoCommand
{
    [CmdletBinding()]
	param
	(
		[parameter(Mandatory = $true)]
		[System.Xml.XmlElement] 
		$SvnResult
    )

    Process
    {

        $SvnInfo = New-Object PSObject -Property @{
                                                   Path=$($SvnResult.entry.Path)
                                                   CurrentRevision=$($SvnResult.entry.Revision)
                                                   Url=$($SvnResult.entry.url)
                                                   LastChangedRevision=$($SvnResult.entry.commit.revision)
                                                   LastChangedOwner=$($SvnResult.entry.commit.author)
                                                   LastChangedDate=$($SvnResult.entry.commit.date)
                                                  }

        Write-Output $SvnInfo

    }
}

Function Parse-StatusCommand
{
    [CmdletBinding()]
	param
	(
		[parameter(Mandatory = $true)]
		[System.Xml.XmlElement] 
		$SvnResult
    )

    Process
    {

        $SvnStatus = New-Object PSObject -Property @{
                                                   Path=$($SvnResult.target.Path)
                                                   Changes=@()
                                                   }
        

        $SvnStatus | Add-Member -MemberType ScriptProperty -Name HasChanges -Value {$this.Changes.Count -gt 0}

        if ($SvnResult.target.entry)
        {
            foreach($entry in $SvnResult.target.entry)
            {
                $change = New-Object PSObject -Property @{
                                                         Path=$($entry.Path)
                                                         ChangeType=$($entry.'wc-status'.item)
                                                         }
                
                $SvnStatus.Changes += $change

            }

        }

        Write-Output $SvnStatus

    }
}

Function Save-SvnChanges
{
    [cmdletbinding(SupportsShouldProcess=$True)]
	param
	(
		[parameter(Mandatory = $true)]
        [ValidateScript({(Test-Path $_ -PathType Container) -or (Test-Path $_ -PathType Leaf)})] 
		[System.String]
		$LocalPath,

		[parameter(Mandatory = $false)]
	    [System.String]
		$Comment,

		[parameter(Mandatory = $false)]
	    [System.String]
		$UserName,

		[parameter(Mandatory = $false)]
	    [System.String]
		$Password

	)
     Begin
    {
        $watch = [system.diagnostics.stopwatch]::StartNew() 
        write-verbose "Start Save-SvnChanges from $LocalPath"

    }
    End
    {
        write-verbose "End Save-SvnChanges from $LocalPath"
        $watch.Stop();
        Write-Verbose "Function duration : $($watch.Elapsed)"
    }
    Process
    {

        $resultAction = Get-RemoteSvnStatus -Path $LocalPath -UserName $UserName -Password $Password
        if ($resultAction.ExitCode -eq -1)
        {
            throw New-Object System.Exception "Unable to obtain svn status from path $LocalPath with following exception $($resultAction.Exception)"
        }    

        $svnMapFolder = $resultAction.Result

        if (!$svnMapFolder.HasChanges)
        {
            Write-Warning "No changes detection with path $LocalPath"
            Write-Output $resultAction
        }
        else
        {
            $computer = Get-ChildItem Env:\COMPUTERNAME
            $subCommand = "ci"
            $Comment += " Save-SvnChanges from $LocalPath of $($Computer.Value)"

            $SvnCommand = [String]::Format("{0} {1} {2} -m '{3}'", $svnExe, $subCommand, $LocalPath, $Comment)

            $svnStatus = Invoke-SvnCommand -SvnCommand $SvnCommand -UserName $UserName -Password $Password
            Write-Output $svnStatus
        }

        
    }
}

Function Ensure-SvnCredential
{
    [cmdletbinding(SupportsShouldProcess=$True)]
	param
	(
		[parameter(Mandatory = $true)]
		[System.String]
		$SvnCommand,

		[parameter(Mandatory = $false)]
	    [System.String]
		$UserName,

		[parameter(Mandatory = $false)]
	    [System.String]
		$Password

	)
    Process
    {

        if (![String]::IsNullOrEmpty($UserName))
        {
            Write-Verbose "UserName is : $UserName"
            $svnCommand += " --username $UserName"
        }

        if (![String]::IsNullOrEmpty($Password))
        {
            Write-Verbose "Password is : *********"

            if ($Password.Contains("`$"))
            {
                #write-warning "Password has powershell specific characters, so they will be escapes"
                $Password = $Password.Replace("`$","``$")
                #Write-Warning "New password is : $Password"
            }


            $svnCommand += " --password $Password"

        }

        Write-Output $SvnCommand

    }
}




Function Add-ObjectToSvn
{
    [cmdletbinding(SupportsShouldProcess=$True)]
	param
	(
		[parameter(Mandatory = $true)]
		[System.String]
		$Path,

		[parameter(Mandatory = $false)]
	    [System.String]
		$UserName,

		[parameter(Mandatory = $false)]
	    [System.String]
		$Password

	)
     Begin
    {
        $watch = [system.diagnostics.stopwatch]::StartNew() 
        write-verbose "Start Add-ObjectToSvn $Path"
        $Path = EnsureCorrectPath -Path $Path

    }
    End
    {
        write-verbose "End Add-ObjectToSvn $Path"
        $watch.Stop();
        Write-Verbose "Function duration : $($watch.Elapsed)"
    }
    Process
    {

        $subCommand = "add"

        $SvnCommand = [String]::Format("{0} {1} {2}", $svnExe, $subCommand, $Path)
        
        $svnStatus = Invoke-SvnCommand -SvnCommand $SvnCommand -UserName $UserName -Password $Password
        Write-Output $svnStatus
    }
}



Function New-SvnDirectory
{
    [cmdletbinding(SupportsShouldProcess=$True)]
	param
	(
		[parameter(Mandatory = $true)]
		[System.String]
		$DirectoryName,

		[parameter(Mandatory = $true)]
		[System.Uri]
		$ParentUri,

		[parameter(Mandatory = $false)]
	    [System.String]
		$Comment,

		[parameter(Mandatory = $false)]
	    [System.String]
		$UserName,

		[parameter(Mandatory = $false)]
	    [System.String]
		$Password

	)
     Begin
    {
        $watch = [system.diagnostics.stopwatch]::StartNew() 
        write-verbose "Start New-SvnDirectory for  $DirectoryName in $($ParentUri.AbsoluteUri)"

    }
    End
    {
        write-verbose "End New-SvnDirectory for  $DirectoryName in $($ParentUri.AbsoluteUri)"
        $watch.Stop();
        Write-Verbose "Function duration : $($watch.Elapsed)"
    }
    Process
    {

        $subCommand = "mkdir"

        $FulluriPath = [System.IO.Path]::COmbine($ParentUri.AbsoluteUri,$DirectoryName)
        $FulluriPath = $FulluriPath.Replace("\","/") 

        $Comment += " Create new Directory $DirectoryName in $($ParentUri.AbsoluteUri)"

        $SvnCommand = [String]::Format("{0} {1} {2} --parents -m '{3}'", $svnExe, $subCommand, $FulluriPath, $Comment)
        
        $svnStatus = Invoke-SvnCommand -SvnCommand $SvnCommand -UserName $UserName -Password $Password

        Write-Output $svnStatus
    }
}

function Sync-WorkspaceToSVN
{
    [cmdletbinding(DefaultParameterSetName="none")]
	param
	(
		[parameter(Mandatory = $true)]
        [ValidateScript({Test-Path $_ -PathType Container})] 
        [System.String]
        $WorkspacePath,

		[parameter(Mandatory = $true)]
        [System.String]
        $Comment,

		[parameter(Mandatory = $false)]
	    [System.String]
		$UserName,

		[parameter(Mandatory = $false)]
	    [System.String]
		$Password
	)
    Begin
    {
        $watch = [system.diagnostics.stopwatch]::StartNew() 
        write-verbose "Start Sync-WorkspaceToSVN from $WorkspacePath"
    }
    End
    {
        write-verbose "End Sync-WorkspaceToSVN from $WorkspacePath"
        $watch.Stop();
        Write-Verbose "Function duration : $($watch.Elapsed)"
    }
    Process
    {
        #Identify if the package is new or changed 
        $svnStatus = Get-RemoteSvnStatus -Path $WorkspacePath -UserName $UserName -Password $Password
        $svnMapFolder = $svnStatus.Result

        if ($svnMapFolder.HasChanges)
        {
            foreach($change in $svnStatus.Result.Changes)
            {
                #Add new item
                if ($change.ChangeType -eq 'unversioned')
                {
                    Add-ObjectToSvn -Path $change.Path
                }
            }

            #Commit into SVN
            $svnStatus = Save-SvnChanges -LocalPath $WorkspacePath -Comment $Comment -UserName $UserName -Password $Password

            Write-Output $svnStatus
        }

    }

}


Function Copy-SvnItem
{
    [cmdletbinding(SupportsShouldProcess=$True)]
	param
	(
		[parameter(Mandatory = $true)]
		[System.String]
		$SourceUri,

		[parameter(Mandatory = $true)]
	    [System.String]
		$TargetUri,

		[parameter(Mandatory = $false)]
	    [System.String]
		$Comment,

		[parameter(Mandatory = $false)]
	    [System.String]
		$UserName,

		[parameter(Mandatory = $false)]
	    [System.String]
		$Password

	)
     Begin
    {
        $watch = [system.diagnostics.stopwatch]::StartNew() 
        write-verbose "Start Copy-SvnItem from $SourceUri to $TargetUri"

    }
    End
    {
        write-verbose "End Copy-SvnItem from $SourceUri to $TargetUri"
        $watch.Stop();
        Write-Verbose "Function duration : $($watch.Elapsed)"
    }
    Process
    {

        $computer = Get-ChildItem Env:\COMPUTERNAME
        $subCommand = "cp"
        $Comment += " Copy-SvnItem from $SourceUri to $TargetUri of $($Computer.Value)"

        $SvnCommand = [String]::Format("{0} {1} {2} {3} --parents -m '{4}'", $svnExe, $subCommand, $SourceUri, $TargetUri, $Comment)

        $svnCopyResult = Invoke-SvnCommand -SvnCommand $SvnCommand -UserName $UserName -Password $Password
        Write-Output $svnCopyResult
        
    }
}



Function EnsureCorrectPath
{
	param
	(
		[parameter(Mandatory = $true)]
		[System.String]
		$Path
    )
        
    #TODO : Check path with space isn't full implemented
    if ($Path.Contains(" ") -and (!$Path.StartsWith("'") -or !$Path.StartsWith('"')) -and (!$Path.EndsWith("'") -or !$Path.EndsWith('"')))
    {
        return [System.String]::Format("'{0}'", $Path)
    }
    else
    {
        return $Path
    }

}


